#!/usr/bin/env python
import sys
import csv
import os
import base64
import getpass
import getopt
import platform
import re
import urllib
import urllib2
import xml.etree.ElementTree as ET

client_id      = "Abn2Wesabe-Uploader"  # Application name
client_version = "0.2"                  # Application version number
system_name    = platform.system()
system_release = platform.release()
api_version    = "1.0.0"                # Document which API version I am using

user_agent     = "%s/%s (%s %s) Wesabe-API/%s" % \
                 (client_id, client_version, system_name, system_release,
                 api_version)
accounts_list  = {}
accounts_url   = "https://www.wesabe.com/accounts.xml"
upload_url     = "https://api.wesabe.com/rest/upload/statement"

# Functions and classes

def ask_for_credentials():
    sys.stdout.write("Wesabe username: ")
    username = sys.stdin.readline().rstrip()
    password = getpass.getpass("Password: ")
    return (username, password)

def ask_for_accid():
    sys.stdout.write("Id of the account to be updated: ")
    id = sys.stdin.readline().rstrip()
    return id

##
# Makes a request to Wesabe and returns the list of bank accounts
# (filtering cash ones) corresponding to the given user
#
# @param username Wesabe Username
# @param password Password for the user
# @return List of dictionaries containing account information
def get_accounts_list(username, password):
    credentials = base64.encodestring('%s:%s' % (username, password))[:-1]
    request     = urllib2.Request(accounts_url)
    request.add_header("User-Agent", user_agent)
    request.add_header("Authorization", "Basic %s" % credentials)

    stream   = urllib2.urlopen(request)
    response = stream.read()
    stream.close()

    return [
                {
                'id'        : account.find('id').text,
                'number'    : account.find('account-number').text,
                'type'      : account.find('account-type').text,
                'wesabe_id' : account.find('financial-institution').find('id').text,
                'name'      : account.find('name').text,
                'balance'   : account.find('current-balance').text
                }
                # Filtering out the Cash accounts
                for account in ET.fromstring(response).findall('account')
                if account.find('account-type').text != 'Cash'
           ]

##
# Uploads the actual transaction list information into Wesabe
#
# @param xml Statement upload XML, which includes the QIF contents
def upload_data(xml, acctid, accttype, balance, wesabe_id, username, password):
    root = ET.Element('upload')
    statement = ET.SubElement(root, 'statement', acctid=acctid, accttype=accttype, balance=balance, wesabe_id=wesabe_id)
    statement.text = xml
    credentials = base64.encodestring('%s:%s' % (username, password))[:-1]
    headers = {
                "Authorization" : "Basic %s" % credentials,
                "User-Agent" : user_agent,
                "Content-type" : "application/xml",
                "Accept" : "*/* application/xml"
              }
    request = urllib2.Request(upload_url, ET.tostring(root), headers)
    return urllib2.urlopen(request).read()

##
# The following function uses part of the "ABN Amro 2 QIF Converter" by Johan Kool.
# More information at http://www.johankool.nl/software/abnamro2qif/
#
# It converts ABN AMRO bank statements into qif files.
#
# @param inputfile The statement file generated by the bank
# @param tempfile Temporary file
# @return string containing the QIF converted statement

def convert2qif(inputfile_name):
    output = "!Type:Bank\n"
    inputfile = open(inputfile_name, 'rb')
    tempfile_name = inputfile_name + '.temp'
    tempfile = open(tempfile_name, 'w')

    class Custom(csv.excel_tab):
        skipinitialspace = True
        delimiter = '\t'

    csv.register_dialect('custom', Custom)

    # add account	currency	date1	misc1	misc2	date2	amount	description line to inputfile
    tempfile.write("account\tcurrency\tdate1\tmisc1\tmisc2\tdate2\tamount\tdescription\r\n")

    # zap useless ascii control characters
    identity_map = ''.join([chr(x) for x in range(256)])
    bad_map = ''.join([chr(x) for x in range(8)] + [chr(x) for x in range(11,32)] + [chr(124)])

    clean_text = inputfile.read().translate(identity_map, bad_map)

    tempfile.write(clean_text)
    tempfile.close()

    tempfile = open(tempfile_name, 'rb')

    reader = csv.DictReader(tempfile, dialect='custom')

    for row in reader:
        # convert date from yyyymmdd to dd/mm/yyyy
        date = row["date1"][6:8]+"/"+row["date1"][4:6]+"/"+row["date1"][0:4]
        # convert amount from -nnn,nn to -nnn.nn
        amount = row["amount"].replace(",",".")
        data = "D%s\nP%s\nT%s\n^\n" % (date, row["description"], amount)
        output += data

    # cleanup temp file
    os.remove(tempfile_name)
    return output

def uploadFile(username, password, inputfile_name):
    finalbalance = ''
    for account in get_accounts_list(username, password):
        accounts_list[account['id']] = account
        print "%s:\t%s\t%s" % (account['id'], account['name'], account['balance'])

    chosen_acc = ask_for_accid()
    if (chosen_acc in accounts_list):
        print "Uploading data to Wesabe account..."
        try:
            # Get current final balance from bank statement
            inputfile = open(inputfile_name, 'rb')
            r = re.compile("^\d+\s[A-Z]{3}\s\d{8}\s[\d,]+\s(\d+,\d+)")
            finalbalance = re.match(r, inputfile.read()).group(1).replace(',', '.')
            inputfile.close()
        except:
            print >>sys.stderr, "\Warning:\nThe final balance could not be retrieved."

        acc = accounts_list[chosen_acc]
        return upload_data(convert2qif(inputfile_name),
                    acc['number'],
                    acc['type'],
                    finalbalance,
                    acc['wesabe_id'],
                    username, password)
    else:
        return ''

def main(argv=None):
    if argv is None:
        argv = sys.argv

    if len(argv) < 2:
        print >>sys.stderr, "No statement file provided\nUsage: abn2wesabe STATEMENTFILE.TAB"
        sys.exit()

    inputfile_name = argv[1]
    accountlist = ''
    while 1:
        try:
            (username, password) = ask_for_credentials()
            accountlist = uploadFile(username, password, inputfile_name)
            if not accountlist == '':
                if ET.fromstring(accountlist).get('status') == 'failed':
                    print >>sys.stderr, "Error: Wesabe could not process the transaction."
                else:
                    print "Success!"
                break
            else:
                print >>sys.stderr, "\nError:\nWrong account ID. Try again."
        except urllib2.HTTPError, ex:
            if (ex.code == 401):
                print >>sys.stderr, "The username and password combination is not valid, try again."
        except:
            print >>sys.stderr, "Unexpected error: ", sys.exc_info()[0]
            break

if __name__ == "__main__":
    sys.exit(main())
